%include "arch/x86/boot.inc"

[bits 16]
section Loader vstart=LOADER_BASE_ADDR
START:
    ;xor ah,ah
    ;mov al,13h
    ;int 10h;320 * 200 256 colors graphics mode

    ; ========================================================
    ; Probe_memory
    ; ------------  int 15h eax = 0000E820h ,edx = 534D4150h ('SMAP')   ------------
    xor ebx, ebx                              ; ebx: first call with 0
    mov edx, 0x534d4150                    ; edx： signature
    mov di, ards_buf                       ; ards structure buffer
.e820_mem_get_loop:                           ; parse each ARDS
    mov eax, 0x0000e820                      ; update function number because each call changes eax with SMAP 0X534D4150
    mov ecx, 20                              ; ARDS size is 20 byte
    int 0x15
    jc .e820_failed_so_try_e801              ; CF==1 means error ,so we try 0xe801 bios function
    add di, cx                               ; add di with 20 to point di to new ARDS
    inc word [ards_num]                      ; record ARDS number
    cmp ebx, 0                               ; if ebx==0&&cf!=1 means it is the last ARDS
    jnz .e820_mem_get_loop

    ; from all ARDS, find the maximum of (base_add_low + length_low), which equals the capacity of mem。
    mov cx, [ards_num]                         ; ARDS number
    mov ebx, ards_buf
    xor edx, edx                               ; edx means max mem , init it with 0
.find_max_mem_area:
    mov eax, [ebx]                             ; base_add_low
    add eax, [ebx+8]                           ; length_low
    add ebx, 20                                ; next ARDS
    cmp edx, eax
    jge .next_ards
    mov edx, eax
.next_ards:
    loop .find_max_mem_area
    jmp .mem_get_ok

   ; -----------  int 15h ax = E801h ----------
.e820_failed_so_try_e801:
    mov ax,0xe801
    int 0x15
    jc .e801_failed_so_try88

       ; 1 calc 0~15M mem, ax and cx based on KB
    mov cx,0x400                               ; convert cx and ax to byte
    mul cx
    shl edx,16
    and eax,0x0000FFFF
    or edx,eax
    add edx, 0x100000                          ; edx plus 1MB mem
    mov esi,edx                                ; backup

                                              ; 2 calc 16MB~ mem ,bx and dx based on 64KB
    xor eax,eax
    mov ax,bx
    mov ecx, 0x10000                           ; 64KB
    mul ecx                                    ; save high 32bit to edx,low 32 bit to eax.
    add esi,eax                                ; only 4GB can detected, so only use eax
    mov edx,esi                                ; edx: total mem
    jmp .mem_get_ok

   ; -----------------  int 15h ah = 0x88 get mem less than 64MB  ----------
.e801_failed_so_try88:
    mov  ah, 0x88
    int  0x15
    jc .error_hlt
    and eax,0x0000FFFF
    mov cx, 0x400                              ; change kb to byte
    mul cx
    shl edx, 16
    or edx, eax                                ; 把积的低16位组合到edx,为32位的积
    add edx,0x100000                           ; 0x88 function onlt return size of mem larger than 1MB,so add 1MB additionally

.mem_get_ok:
    mov [total_mem_bytes], edx                 ; 将内存换为byte单位后存入total_mem_bytes处。

    in al,92h
    or al,00000010b
    out 92h,al;open A20 mode

    lgdt [gdt_ptr]

    mov eax,cr0
    or eax,1
    mov cr0,eax

    jmp word 0x0008:protected_mode

.error_hlt:                                   ; halt if error
    hlt

[bits 32]
protected_mode:
    mov ax,0x0010
    mov ds,ax
    mov es,ax
    mov ss,ax
    mov esp,0x80000
    mov ebp,esp
    mov ax,0x0018
    mov gs,ax
    call create_pages
    call readdisk_m32
    ;载入PE程序
    mov ebx,COFF_BASE_ADDR
    cmp word [ebx],0x5a4d
    jnz Error
    mov eax,[ebx+60]
    add ebx,eax
    cmp dword [ebx],0x00004550
    jnz Error
    cmp word [ebx+20],0xE0
    jnz Error
    push dword [ebx+40]
    mov edx,[ebx+52]
    xor ecx,ecx
    mov cx,[ebx+6]
    jcxz Error
    add ebx,0xF8
    xor esi,esi
    .load_seg:
        push ecx
        mov edi,[ebx+12]
        mov ecx,[ebx+16]
        mov esi,[ebx+20]
        add esi,COFF_BASE_ADDR
        shr ecx,2
        add edi,edx
        rep movsd
        pop ecx
        add ebx,40
        loop .load_seg
    pop eax
    add eax,edx
    lea esp,[edx+0c0000000h]
    mov ebp,esp
    fninit
    ;fwait
    call eax

    in al,92h
    and al,11111101b
    out 92h,al

    mov eax,cr0
    and al,0xfe
    mov cr0,eax

    jmp word 0xffff:0
Error:
    not byte [gs:0x01]
    hlt
create_pages:
    mov edi,ADDRESS_PTE
    mov ecx,400h
    xor eax,eax
    rep stosd
    mov edi,ADDRESS_PTE + 0c00h
    mov ecx,USED_PTE
    mov eax,ADDRESS_PTE + 1007h
    .create_pte1:
        stosd
        add eax,1000h
        loop .create_pte1
    mov edi,ADDRESS_PTE
    mov ecx,USED_PTE
    mov eax,ADDRESS_PTE + 1007h
    .create_pte2:
        stosd
        add eax,1000h
        loop .create_pte2
    mov edi,ADDRESS_PTE + 1000h
    mov ecx,USED_PTE * 0x400
    mov eax,7
    .create_pde:
        stosd
        add eax,1000h
        loop .create_pde
    mov eax,ADDRESS_PTE
    mov cr3,eax
    mov eax,cr0
    or eax,80000000h
    mov cr0,eax
    ret
readdisk_m32:
    mov dx,0x1f2
    mov al,COFF_SECTOR_NUMBER
    out dx,al
    mov eax,COFF_START_SECTOR
    mov dx,0x1f3
    out dx,al
    mov dx,0x1f4
    mov cl,8
    shr eax,cl
    out dx,al
    mov dx,0x1f5
    shr eax,cl
    out dx,al
    mov dx,0x1f6
    shr eax,cl
    and al,0xf
    or al,0xe0
    out dx,al
    mov dx,0x1f7
    mov al,0x20
    out dx,al
    .wait_loop:
        nop
        in al,dx
        and al,0x88
        cmp al,0x8
        jnz .wait_loop
    mov ax,ds
    mov es,ax
    mov ecx,COFF_SECTOR_NUMBER * 0x80
    mov edi,COFF_BASE_ADDR
    mov dx,0x1f0
    rep insd
    ret

gdt_ptr:
    dw gdt_end - gdt_start - 1
    dd gdt_start
gdt_start:
    dd 0
    dd 0
    dd (0x0000 << 16) | 0xFFFF
    dd (0x00 << 24) | DESC_G_4K | DESC_D_32 | DESC_P | (0xF << 16) | DESC_DPL_0 | DESC_TYPE_CODE | DESC_CODE_READABLE | 0x00
    dd (0x0000 << 16) | 0xFFFF
    dd (0x00 << 24) | DESC_G_4K | DESC_D_32 | DESC_P | (0xF << 16) | DESC_DPL_0 | DESC_TYPE_DATA | DESC_DATA_WRITABLE | 0x00
    dd (0x8000 << 16) | 0x1040
    dd (0x00 << 24) | DESC_D_32 | DESC_P | (0x0 << 16) | DESC_DPL_0 | DESC_TYPE_DATA | DESC_DATA_WRITABLE | 0x0B
    dd (0x0000 << 16) | 0xFFFF
    dd (0x00 << 24) | DESC_G_4K | DESC_D_32 | DESC_P | (0xB << 16) | DESC_DPL_3 | DESC_TYPE_CODE | DESC_CODE_READABLE | 0x00
    dd (0x0000 << 16) | 0xFFFF
    dd (0x00 << 24) | DESC_G_4K | DESC_D_32 | DESC_P | (0xB << 16) | DESC_DPL_3 | DESC_TYPE_DATA | DESC_DATA_WRITABLE | 0x00
gdt_end:

total_mem_bytes equ 0x500
ards_num dw 0
ards_buf times 50 dd 0

;intr_name[0] = "#DE Divide Error";
;intr_name[1] = "#DB Debug Exception";
;intr_name[2] = "NMI Interrupt";
;intr_name[3] = "#BP Breakpoint Exception";
;intr_name[4] = "#OF Overflow Exception";
;intr_name[5] = "#BR BOUND Range Exceeded Exception";
;intr_name[6] = "#UD Invalid Opcode Exception";
;intr_name[7] = "#NM Device Not Available Exception";
;intr_name[8] = "#DF Double Fault Exception";
;intr_name[9] = "Coprocessor Segment Overrun";
;intr_name[10] = "#TS Invalid TSS Exception";
;intr_name[11] = "#NP Segment Not Present";
;intr_name[12] = "#SS Stack Fault Exception";
;intr_name[13] = "#GP General Protection Exception";
;intr_name[14] = "#PF Page-Fault Exception";
;intr_name[15] reserved.
;intr_name[16] = "#MF x87 FPU Floating-Point Error";
;intr_name[17] = "#AC Alignment Check Exception";
;intr_name[18] = "#MC Machine-Check Exception";
;intr_name[19] = "#XF SIMD Floating-Point Exception";